package com.example.mall.order.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mall.common.utils.PageUtils;
import com.example.mall.common.utils.Query;
import com.example.mall.order.dao.OrderDao;
import com.example.mall.order.entity.OrderEntity;
import com.example.mall.order.entity.OrderItemEntity;
import com.example.mall.order.feign.CouponFeignService;
import com.example.mall.order.service.OrderService;
import com.example.mall.order.vo.CouponVO;
import com.rabbitmq.client.Channel;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.Map;

@Slf4j
@RabbitListener(queues = {"two.two"})
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    @Autowired
    private CouponFeignService couponFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 追加-测试分布式事务的Demo 202306292322
     * 一、登录seata官网，在本地数据库中每个库新增`undo_log`表
     * 二、D:\Seata\seata-server-1.6.1\seata\conf\application.yml 中 seata.config.type和seata.registry.type都改为nacos
     * 三、引入spring-cloud-starter-alibaba-seata和seata-spring-boot-starter依赖
     * 四、涉及到分布式事务的服务都使用DataSourceProxy来代理数据源（可跳过，因为在SeataAutoDataSourceProxyCreator buildProxy中已代理）
     * 五、在方法入口加上@GlobalTransactional注解；@Transactional可不加，加了也会失效，发生异常时就算是Propagation.REQUIRES_NEW也会回滚
     * *
     * *
     * 测试事务的Demo 202306292250
     * 预期结果：OrderEntity第一次新增回滚，第二次新增不回滚；远程调用CouponEntity新增成功不回滚
     *
     * @param order OrderEntity
     * @return int
     */
    @GlobalTransactional
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int addOrder(OrderEntity order) {
        log.info("=========== 全局事务方法开始 ===========");
        log.info("=========== addOrder 开始 ===========" + RootContext.getXID());
        int result = addOrderEntity(order);

        // 直接调用方法事务会失效，因为调用的不是代理对象，用 AopContext.currentProxy() 代替
        // 使用 AopContext 时需要加上注解并设置exposeProxy属性为true @EnableAspectJAutoProxy(exposeProxy = true)，调用时会报错提醒
//        testTransactionRollback(order);
        OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy();
        orderService.testTransactionRollback(order);

        addCoupon();
        if (result == 1) {
            throw new RuntimeException("模拟异常场景，判断是否回滚！");
        }
        log.info("=========== 全局事务方法结束 ===========");
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void testTransactionRollback(OrderEntity order) {
        log.info("=========== testTransactionRollback ===========");
        order.setId(100L);
        addOrderEntity(order);
    }

    private int addOrderEntity(OrderEntity order) {
        log.info("=========== addOrderEntity 开始 ===========");
        int result = this.baseMapper.insert(order);
        if (result != 1) {
            log.info("插入失败！[{}]", JSON.toJSONString(order));
            throw new RuntimeException("插入记录[" + order.getId() + "]失败！");
        }
        log.info("=========== addOrderEntity 结束 ===========");
        return 1;
    }

    private void addCoupon() {
        CouponVO couponVO = new CouponVO();
        couponVO.setCouponName("呀呀呀优惠券");
        couponVO.setNote("嘿嘿嘿");
        couponFeignService.add(couponVO);
    }

    /**
     * 一、多个@RabbitListener监听同一个队列，则会轮流消费消息
     *
     * @param orderEntity OrderEntity
     */
//    @RabbitListener(queues = {"world"})
//    public void listenerMessage(OrderEntity orderEntity) {
//        log.info("接受到[world]队列消息[OrderEntity]：{}", JSON.toJSONString(orderEntity));
//    }

    /**
     * 二、多个@RabbitListener监听同一个队列，则会轮流消费消息
     *
     * @param orderItemEntity OrderItemEntity
     */
//    @RabbitListener(queues = {"world"})
//    public void listenerMessage(OrderItemEntity orderItemEntity) {
//        log.info("接受到[world]队列消息[OrderItemEntity]：{}", JSON.toJSONString(orderItemEntity));
//    }


    /**
     * * @RabbitListener配合@RabbitHandler注解，保证收到的消息类型与发送的消息类型一致，相当于重载
     * 例如：若发送的消息对象为OrderEntity，则该方法才会接收该消息
     *
     * @param orderEntity OrderEntity
     */
    @RabbitHandler
    public void handlerMessage(Message message, OrderEntity orderEntity, Channel channel) throws IOException {
        log.info("接受到[two.two]队列消息[message]：{}", message);
        log.info("接受到[two.two]队列消息[OrderEntity]：{}", JSON.toJSONString(orderEntity));
        log.info("接受到[two.two]队列消息[channel]：{}", channel);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        if (deliveryTag % 2 == 0) {
            // 第二个参数：是否批量处理
            channel.basicAck(deliveryTag, false);
            log.info("手动签收消息：[{}]", deliveryTag);
        } else {
            // 第二个参数：是否批量处理；第三个参数：是否将消息重新放回队列
            channel.basicNack(deliveryTag, false, true);
            log.info("手动拒收消息：[{}]", deliveryTag);
        }
    }

    /**
     * * @RabbitListener配合@RabbitHandler注解，保证收到的消息类型与发送的消息类型一致，相当于重载
     * 例如：若发送的消息对象为OrderItemEntity，则该方法才会接收该消息
     *
     * @param orderItemEntity OrderItemEntity
     */
    @RabbitHandler
    public void handlerMessage(OrderItemEntity orderItemEntity) {
        log.info("接受到[two.two]队列消息[OrderItemEntity]：{}", JSON.toJSONString(orderItemEntity));
    }

}